/*
 * Copyright (C) Michael Martin & Antonio Carrozzini (TBJD)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package org.tbjd.client.borderDetectors;

import java.util.ArrayList;
import java.util.List;

import org.tbjd.client.interfaces.BorderListener;
import org.tbjd.client.interfaces.BorderNotifier;

import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;

/**
 * <p>
 * The GanttBorderNotiifer is in charge of notifying {@link org.tbjd.client.interfaces.BorderListener BorderListeners} when a border is detected on the Gantt scroll area of the
 * {@link org.tbjd.client.guis.ApplicationLayoutGui ApplicationLayoutGui}.
 * </p>
 * 
 * @author Michael Martin
 * @author Antonio Carrozzini
 * @version 1.0
 */
public class GanttBorderNotifier extends Timer implements BorderNotifier {

	private static GanttBorderNotifier instance;

	private List<BorderListener> listeners = null;

	private int INTERVAL = 100;
	private int ZONE_WIDTH = 50;

	private boolean northActive = false;
	private boolean southActive = false;
	private boolean eastActive = true;
	private boolean westActive = true;

	private int directionX, directionY;
	private boolean isRunning = false;
	private Widget widgetsContainer = null;
	private Widget draggable = null;

	private GanttBorderNotifier() {
		this.listeners = new ArrayList<BorderListener>();
	}

	public void mouseEvent(Widget widgetsContainer, Widget draggable, int x, int y) {
		this.widgetsContainer = widgetsContainer;
		this.draggable = draggable;
		if (isInZone(x, y)) {
			if (!isRunning()) {
				run();
			}
		} else {
			cancel();
			this.isRunning = false;
		}
	}

	/**
	 * Tell if the mouse location is in a sensitive zone (a border).
	 * @param mouseX the mouse x position
	 * @param mouseY the mouse y position
	 * @return a boolean if in zone or not
	 */
	private boolean isInZone(int mouseX, int mouseY) {
		this.directionX = 0;
		this.directionY = 0;

		if (northActive && isNorth(mouseY)) {
			this.directionY = 1;
		}
		if (southActive && isSouth(mouseY)) {
			this.directionY = -1;
		}
		if (westActive && isWest(mouseX)) {
			this.directionX = 1;
		}
		if (eastActive && isEast(mouseX)) {
			this.directionX = -1;
		}

		return this.directionX != 0 || this.directionY != 0;
	}

	private boolean isNorth(int mouseY) {
		return (mouseY > this.widgetsContainer.getAbsoluteTop() && mouseY < this.widgetsContainer.getAbsoluteTop() + ZONE_WIDTH);
	}

	private boolean isWest(int mouseX) {
		return (mouseX > this.widgetsContainer.getAbsoluteLeft()) && (mouseX < this.widgetsContainer.getAbsoluteLeft() + ZONE_WIDTH);
	}

	private boolean isEast(int mouseX) {
		return (mouseX > (this.widgetsContainer.getAbsoluteLeft() + this.widgetsContainer.getOffsetWidth() - ZONE_WIDTH) && (mouseX < (this.widgetsContainer
				.getOffsetWidth() + this.widgetsContainer.getAbsoluteLeft())));
	}

	private boolean isSouth(int mouseY) {
		return (mouseY > this.widgetsContainer.getAbsoluteTop() + this.widgetsContainer.getOffsetHeight() - ZONE_WIDTH)
				&& (mouseY < this.widgetsContainer.getAbsoluteTop() + this.widgetsContainer.getOffsetHeight());
	}

	public void run() {
		scheduleRepeating(INTERVAL);
		this.isRunning = true;
	}

	private boolean isRunning() {
		return this.isRunning;
	}

	public void scheduleRepeating(int periodMillis) {
		super.scheduleRepeating(periodMillis);
		for (BorderListener listener : this.listeners) {
			listener.executeBorderDetected(draggable, directionX, directionY);
		}
	}

	/**
	 * Returns the interval specified for this BorderNotifier.
	 * @return the interval value in milliseconds.
	 */
	public int getInterval() {
		return INTERVAL;
	}

	/**
	 * <p>
	 * Sets the time interval in milliseconds at which {@link org.tbjd.client.interfaces#BorderListener BorderListener}s are notified.
	 * </p>
	 * <p>
	 * The higher the interval parameter is, the slower the notify will occur.
	 * </p>
	 * 
	 * @param interval
	 */
	public void setInterval(int interval) {
		INTERVAL = interval;
	}

	/**
	 * Returns the sensitive zone width (the border)
	 * @return the border width in pixels
	 */
	public int getBorderWidth() {
		return ZONE_WIDTH;
	}

	/**
	 * Sets the border widths (the sensitive zone) in pixels
	 * @param width the size of the border in pixels
	 */
	public void setBorderWidth(int width) {
		ZONE_WIDTH = width;
	}

	/**
	 * Tell if the north border is sensitive or not
	 * @return true if north is sensitive, false if not
	 */
	public boolean isNorthActive() {
		return northActive;
	}
	
	/**
	 * Sets the north border as active
	 * @param northActive true if north is active
	 */
	public void setNorthActive(boolean northActive) {
		this.northActive = northActive;
	}
	
	/**
	 * Tell if the south border is sensitive or not
	 * @return true if south is sensitive, false if not
	 */
	public boolean isSouthActive() {
		return southActive;
	}
	
	/**
	 * Sets the south border as active
	 * @param southActive true if south is active
	 */
	public void setSouthActive(boolean southActive) {
		this.southActive = southActive;
	}
	
	/**
	 * Tell if the east border is sensitive or not
	 * @return true if north is sensitive, false if not
	 */
	public boolean isEastActive() {
		return eastActive;
	}
	
	/**
	 * Sets the east border as active
	 * @param eastActive true if east is active
	 */
	public void setEastActive(boolean eastActive) {
		this.eastActive = eastActive;
	}
	
	/**
	 * Tell if the west border is sensitive or not
	 * @return true if west is sensitive, false if not
	 */
	public boolean isWestActive() {
		return westActive;
	}
	
	/**
	 * Sets the west border as active
	 * @param westActive true if west is active
	 */
	public void setWestActive(boolean westActive) {
		this.westActive = westActive;
	}

	public static GanttBorderNotifier getInstance() {
		if (instance == null) {
			instance = new GanttBorderNotifier();
		}

		return instance;
	}

	public void addBorderListener(BorderListener listener) {
		this.listeners.add(listener);
	}

	public void removeBorderListener(BorderListener listener) {
		this.listeners.remove(listener);
	}

}
